function observe(data) {
  if(Object.prototype.toString.call(data) === '[object Object]'){
    for(let prop in data) {
      defineReactive(data, prop, data[prop])
    }
  }
}

function defineReactive(obj, key,val) {
  Reflect.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get(){
      return val
    },
    set(newVal) {
      if(newVal === val) return
      val = newVal
      observe(newVal)
    }
  })
  observe(val)
}

// -------- Compiler ----------
//把{{ }}之外的内容用字符串原封不动地输出，把{{ }}之内的代码作为 js 表达式输出，然后把它们拼接到一起
function textToExp(text) {
  let pieces = text.split(/({{.+?}})/g) 
  //把"Hello {{name}}, welcome to {{place}}!"拆分成
  // [
  //   'Hello ',
  //   '{{name}}',
  //   ', welcome to ',
  //   '{{place}}',
  //   '!'
  // ]

  pieces = pieces.map(item => {
    if(item.match(/{{|}}$/g)) {
      item = '(' + item.replace(/^{{|}}$/g, '') + ')' // 把{{}}替换成（）
    }else {
      // {{}} 外的代码，以字符串输出
      // 需要对字符串中的`转义
      item = '`'+item.replace(/'/g, '\\`') + '`' 
    }
    return item
  })
  return pieces.join('+')
}

let data = { text: 'world' };

function expToFunc(exp, scope) {
  // 把数据源绑定到函数的 this，函数内部加上 with(this){}，这样函数内的变量访问的就是数据源上的 key
  return new Function('with(this){return ' + exp + '}').bind(scope);
}

let fn = expToFunc('`hello `+( text )+` !`', data);

console.log(fn());

// 1. 数据响应,get,set,递归,在数据更新时，通知所有订阅者
// 2. 每个数据肯定用到多个地方，创建一个Dep依赖容器（添加、删除、通知）
// 3. 把模版{{}}处理成字符串，再转换成可执行的函数，
// 4. 通过递归遍历Dom节点，找到后，执行编译，通过模版中的表达式取值的
// 5. 订阅者，更新方法就是，拿到表达式和数据，执行编译函数，最后替换模版数据

